package cn.chiship.framework.docs.biz.service.impl;

import cn.chiship.framework.common.constants.CommonCacheConstants;
import cn.chiship.framework.docs.biz.entity.FileResources;
import cn.chiship.framework.docs.biz.pojo.dto.ChunkMultipartFileParam;
import cn.chiship.framework.docs.biz.service.FileResourcesService;
import cn.chiship.framework.docs.biz.service.FileService;
import cn.chiship.framework.docs.core.common.DocsCommonConstants;
import cn.chiship.framework.docs.core.properties.FileUploadProperties;
import cn.chiship.sdk.cache.service.RedisService;
import cn.chiship.sdk.cache.vo.CacheUserVO;
import cn.chiship.sdk.core.base.BaseResult;
import cn.chiship.sdk.core.exception.custom.BusinessException;
import cn.chiship.sdk.core.util.RandomUtil;
import cn.chiship.sdk.framework.pojo.vo.UploadVo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.*;

/**
 * @author lijian
 */
@Service
public class FileServiceImpl implements FileService {
    private static final Logger LOGGER = LoggerFactory.getLogger(FileServiceImpl.class);

    public static final String CHUNK_FOLDER = "/CHUNK_FOLDER_TEMP";
    @Resource
    RedisService redisService;
    @Resource
    FileUploadProperties fileUploadProperties;
    @Resource
    FileResourcesService fileResourcesService;

    @Override
    public BaseResult chunkUploadPost(ChunkMultipartFileParam chunk, HttpServletResponse response) {
        MultipartFile file = chunk.getFile();
        Integer chunkNumber = chunk.getChunkNumber();
        String identifier = chunk.getIdentifier();
        byte[] bytes;
        try {
            bytes = file.getBytes();
            // 这里的不同之处在于这里进行了一个保存分块时将文件名的按照-chunkNumber的进行保存
            Path path = Paths.get(generatePath(fileUploadProperties.getFileAbsolutePath(CHUNK_FOLDER), chunk));
            Files.write(path, bytes);
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 这里进行的是保存到redis，并返回集合的大小的操作
        Integer chunks = saveChunk(chunkNumber, identifier);
        // 如果集合的大小和totalChunks相等，判定分块已经上传完毕，进行merge操作
        if (chunks.equals(chunk.getTotalChunks())) {
            return BaseResult.ok(true);
        }
        return BaseResult.ok();
    }

    private static String generatePath(String uploadFolder, ChunkMultipartFileParam chunk) {
        StringBuilder sb = new StringBuilder();
        // 拼接上传的路径
        sb.append(uploadFolder).append(File.separator).append(chunk.getIdentifier());
        //判断uploadFolder/identifier 路径是否存在，不存在则创建
        if (!Files.isWritable(Paths.get(sb.toString()))) {
            try {
                Files.createDirectories(Paths.get(sb.toString()));
            } catch (IOException e) {
                LOGGER.error(e.getMessage(), e);
            }
        }
        // 返回以 - 隔离的分块文件，后面跟的chunkNumber方便后面进行排序进行merge
        return sb.append(File.separator)
                .append(chunk.getFilename())
                .append("-")
                .append(chunk.getChunkNumber()).toString();
    }

    /**
     * 保存信息到Redis
     */
    public Integer saveChunk(Integer chunkNumber, String identifier) {
        String key = CommonCacheConstants.buildKey(CommonCacheConstants.REDIS_CHUNK_IDENTIFIER) + ":" + identifier;
        // 获取目前的chunkList
        Set<Integer> oldChunkNumber = (Set<Integer>) redisService.hget(key, "chunkNumberList");
        // 如果获取为空，则新建Set集合，并将当前分块的chunkNumber加入后存到Redis
        if (Objects.isNull(oldChunkNumber)) {
            Set<Integer> newChunkNumber = new HashSet<>();
            newChunkNumber.add(chunkNumber);
            redisService.hset(key, "chunkNumberList", newChunkNumber);
            // 返回集合的大小
            return newChunkNumber.size();
        } else {
            // 如果不为空，将当前分块的chunkNumber加到当前的chunkList中，并存入Redis
            oldChunkNumber.add(chunkNumber);
            redisService.hset(key, "chunkNumberList", oldChunkNumber);
            // 返回集合的大小
            return oldChunkNumber.size();
        }

    }


    @Override
    public BaseResult chunkUploadGet(ChunkMultipartFileParam chunk) {
        String identifier = chunk.getIdentifier();
        String key = CommonCacheConstants.buildKey(CommonCacheConstants.REDIS_CHUNK_IDENTIFIER) + ":" + identifier;
        if (redisService.hasKey(key)) {
            Set<Integer> chunkNumbers = (Set<Integer>) redisService.hget(key, "chunkNumberList");
            return BaseResult.ok(chunkNumbers);
        }
        return BaseResult.ok();
    }

    @Override
    public BaseResult mergeFile(ChunkMultipartFileParam chunk, CacheUserVO userVO) {
        String fileExt = chunk.getFilename().substring(chunk.getFilename().lastIndexOf(".") + 1);
        String fileName = RandomUtil.uuidLowerCase() + "." + fileExt;
        try {
            String mergeFolder = fileUploadProperties.getFileAbsolutePath(fileUploadProperties.getFilePathByCatalogId(chunk.getCatalogId()));

            String filePath = fileUploadProperties.getFileUrl(fileUploadProperties.getFilePathByCatalogId(chunk.getCatalogId())) + "/" + fileName;
            // 如果合并后的路径不存在，则新建
            if (!Files.isWritable(Paths.get(mergeFolder))) {
                Files.createDirectories(Paths.get(mergeFolder));
            }
            // 合并的文件名
            String target = mergeFolder + File.separator + fileName;
            // 创建文件
            Files.createFile(Paths.get(target));
            // 遍历分块的文件夹，并进行过滤和排序后以追加的方式写入到合并后的文件
            Files.list(Paths.get(fileUploadProperties.getFileAbsolutePath(CHUNK_FOLDER) + File.separator + chunk.getIdentifier()))
                    //过滤带有"-"的文件
                    .filter(path -> path.getFileName().toString().contains("-"))
                    //按照从小到大进行排序
                    .sorted((o1, o2) -> {
                        String p1 = o1.getFileName().toString();
                        String p2 = o2.getFileName().toString();
                        int i1 = p1.lastIndexOf("-");
                        int i2 = p2.lastIndexOf("-");
                        return Integer.valueOf(p2.substring(i2)).compareTo(Integer.valueOf(p1.substring(i1)));
                    })
                    .forEach(path -> {
                        try {
                            //以追加的形式写入文件
                            Files.write(Paths.get(target), Files.readAllBytes(path), StandardOpenOption.APPEND);
                            //合并后删除该块
                            Files.delete(path);
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    });
            String originalName = chunk.getFilename().substring(0, chunk.getFilename().lastIndexOf("."));
            BaseResult baseResult = fileResourcesService.fileResourcesAdd(
                    chunk.getCatalogId(),
                    fileName,
                    originalName,
                    chunk.getTotalSize(),
                    chunk.getFileType(),
                    fileExt,
                    userVO.getId().toString(),
                    userVO.getUsername(),
                    userVO.getRealName(),
                    DocsCommonConstants.STORAGE_LOCATION_LOCAL,
                    null,
                    filePath
            );
            if (!baseResult.isSuccess()) {
                return baseResult;
            }
            String identifier = chunk.getIdentifier();
            String key = CommonCacheConstants.buildKey(CommonCacheConstants.REDIS_CHUNK_IDENTIFIER) + ":" + identifier;
            redisService.del(key);
            FileResources fileResources = (FileResources) baseResult.getData();
            UploadVo uploadVo = new UploadVo();
            uploadVo.setId(fileResources.getId());
            uploadVo.setOriginalName(originalName);
            uploadVo.setUuid(fileResources.getFileUuid());
            return BaseResult.ok(uploadVo);
        } catch (IOException e) {
            throw new BusinessException("文件合并出现错误:" + e.getLocalizedMessage());
        }
    }
}
